#include <arch/interrupts.h>

#include <device/blkio.h>

#include <hal/ahci/hba.h>
#include <hal/ahci/sata.h>

#include <mm/valloc.h>

#define LOG_TAG "IO_EVENT"
#include <log.h>

extern hba_t hba;

void ahci_hba_isr(const isr_param* param) {
    if (!hba.base[HBA_REG_IS]) {
        return;
    }
    u32 port_num = __builtin_ctz(hba.base[HBA_REG_IS]);
    assert(port_num < 32);
    hba_port_t* port = hba.ports[port_num];
    hba_cmd_ctx_t* ctx = &port->cmdctx;
    u32 processed = port->regs[HBA_REG_PxCI] ^ ctx->tracked_ci;

    sata_read_error(port);

    // FIXME When error occurs, CI will not change. Need error recovery!
    if (!processed) {
        LOGE("No command processed! REG_IS: 0x%X, PxCI: 0x%X, Tracked_CI: 0x%X\n", hba.base[HBA_REG_IS], port->regs[HBA_REG_PxCI], ctx->tracked_ci);
        goto done;
    }

    u32 slot = __builtin_ctz(processed);
    assert(slot < 32);
    hba_cmds_t* cmds = ctx->issued[slot];

    if (!cmds) {
        LOGE("Invalid slot %d\n", slot);
        goto done;
    }

    blkio_req_t* ioreq = (blkio_req_t*)cmds->state_ctx;

    if ((port->device->last_result.status & HBA_PxTFD_ERR)) {
        ioreq->errcode = port->regs[HBA_REG_PxTFD] & 0xffff;
        ioreq->flags |= BLKIO_ERROR;
        hba_clear_reg(port->regs[HBA_REG_PxSERR]);
        LOGE("IO error: %x\n", ioreq->errcode);
    }

    ctx->tracked_ci &= ~(1 << slot);
    ctx->issued[slot] = NULL;
    blkio_complete(ioreq);
    blkio_schedule(ioreq->io_ctx);
    vfree_dma(cmds->cmd_table);
    vfree(cmds);

done:
    hba_clear_reg(port->regs[HBA_REG_PxIS]);
    hba.base[HBA_REG_IS] = 1 << port_num;
}

void ahci_blkio_handler(blkio_req_t* req) {
    hba_dev_t* hbadev = req->io_ctx->driver;

    hbadev->ops.submit(hbadev, req);
}